﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Noris.Tools.DbManager.Data
{
	#region class PackageSet
	internal class PackageSet
	{
        #region Konstrukce
        internal PackageSet(string version, string versionSuffix, string author)
		{
			this.Version = version;
			this.VersionSuffix = versionSuffix;
            this.PackageType = Data.PackageVersionType.None;
			this.Author = author;
			this.Items = new List<PackageItem>();
            this.DetectPackageType();
        }
        private void DetectPackageType()
        {
            Data.PackageVersionType pt = Data.PackageVersionType.None;
            string vr = this.Version;
            int vl = vr.Length;
            if (vl == 2 || (vl == 5 && vr.Substring(2,3) == ".00") || (vl == 8 && vr.Substring(2,6) == ".00.00"))
                pt = Data.PackageVersionType.Verse;           // "43" | "43.00" | "43.00.00"
            else if (vl == 5 || (vl == 8 && vr.Substring(5,3) == ".00"))
                pt = Data.PackageVersionType.Patch;           //        "43.15" | "43.15.00"
            else if (vl == 8 && vr.Substring(7,1) == "0")
                pt = Data.PackageVersionType.Hotpatch;        //                  "43.15.20"
            else if (vl == 8 && vr.Substring(7,1) != "0")
                pt = Data.PackageVersionType.HotpatchBeta;    //                  "43.15.25"
            this.PackageType = pt;
        }

        public override string ToString()
        {
            return this.VersionAndSuffix;
        }
        #endregion
        #region Property
        /// <summary>
		/// Verze odvozená z názvu, jen čísla: "43.35",  "43.37.80",  "43.37.86", bez suffixu (HR)
		/// </summary>
		internal string Version { get; private set; }
        /// <summary>
        /// Druh balíčku, odvozený z čísla verze
        /// </summary>
        internal PackageVersionType PackageType { get; private set; }
        /// <summary>
		/// Verze určená ke třídění seznamu: sedmimístná, bez teček, řadí verzi 42 a 43 k sobě (za šestimístnou verzi přidává "2" nebo "3" pro verzi 42/43
		/// </summary>
		internal string VersionSort
		{
			get
			{
				string vers = this.Version.Replace(".", "");
				if (vers.Length < 6)
					vers = vers.PadRight(6, '0');
				if (vers.Substring(0, 2) == "42")
					vers = "43" + vers.Substring(2, 4) + "2";
				else if (vers.Substring(0, 2) == "43")
					vers = "43" + vers.Substring(2, 4) + "3";
                else
                    vers = vers + "0";
				return vers;
			}
		}
		/// <summary>
		/// Suffix verze daný autorem: pro 1 = "", pro 3500000 = " (HR)"
		/// </summary>
		internal string VersionSuffix { get; private set; }
		/// <summary>
		/// Verze a suffix pro vizualizaci
		/// </summary>
		internal string VersionAndSuffix
		{
			get
			{
				string result = this.Version;
				if (!String.IsNullOrEmpty(this.VersionSuffix))
					result += " " + VersionSuffix.Trim();
				return result;
			}
		}
		/// <summary>
		/// Verze + suffix, určeno pro Key do Dictionary
		/// </summary>
		internal string VersionKey { get { return GetVersionKey(this.Version, this.VersionSuffix); } }
		/// <summary>
		/// Autor: "1", "3500000"
		/// </summary>
		internal string Author { get; private set; }
		/// <summary>
        /// Příznak veřejné verze: this.PackageType je typu: Verse, Patch, Hotpatch
		/// </summary>
		internal bool IsPublic
		{
			get
			{
                return (this.PackageType == PackageVersionType.Verse || this.PackageType == PackageVersionType.Patch || this.PackageType == PackageVersionType.Hotpatch);
			}
		}
        /// <summary>
        /// Příznak Beta verze: this.PackageType je typu: HotpatchBeta nebo HotpatchBetaOld
        /// </summary>
        internal bool IsBeta
        {
            get
            {
                return (this.PackageType == PackageVersionType.HotpatchBeta || this.PackageType == PackageVersionType.HotpatchBetaOld);
            }
        }
        /// <summary>
        /// Příznak Beta verze: this.PackageType je typu: HotpatchBetaOld
        /// </summary>
        internal bool IsBetaOld
        {
            get
            {
                return (this.PackageType == PackageVersionType.HotpatchBetaOld);
            }
        }
        /// <summary>
		/// Nejnižší datum Modify všech souborů
		/// </summary>
		internal DateTime? MinWriteTime { get; private set; }
		/// <summary>
		/// Seznam konkrétních souborů s balíčky
		/// </summary>
		internal List<PackageItem> Items { get; private set; }
		/// <summary>
		/// Seznam konkrétních souborů s balíčky, setříděný (základní je první, další dle abecedy)
		/// </summary>
		internal List<PackageItem> ItemsSorted
		{
			get
			{
				List<PackageItem> result = new List<PackageItem>(this.Items);
				result.Sort(PackageItem.CompareByName);
				return result;
			}
		}
        #endregion
        #region Zpracování názvu souboru na verzi, typ verze, autora, suffix, zakázku
        /// <summary>
        /// Rozdělí jméno souboru na verzi, autora, zakázku a z autora určí suffix verze
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="version"></param>
        /// <param name="versionSuffix"></param>
        /// <param name="author"></param>
        /// <param name="customer"></param>
        /// <returns></returns>
		internal static bool ParseFileName(string fileName, out string version, out string versionSuffix, out string author, out string customer)
		{
			version = null;
			versionSuffix = null;
			author = null;
			customer = null;
			string name = System.IO.Path.GetFileNameWithoutExtension(fileName);
			if (name.Length <= 5 || !String.Equals(name.Substring(0, 5), "Noris", StringComparison.InvariantCultureIgnoreCase))
				return false;
			name = name.Substring(5);

			version = _ReadPreDelim(ref name, "_");
			if (version.Length == 4)
				version = version.Substring(0, 2) + "." + version.Substring(2, 2);
			else if (version.Length == 6)
				version = version.Substring(0, 2) + "." + version.Substring(2, 2) + "." + version.Substring(4, 2);

			author = _ReadPreDelim(ref name, "_");
			if (author == "") author = "1";
			versionSuffix = "";
			switch (author)
			{
				case "1":
					break;
				case "3500000":
					versionSuffix = " (HR)";
					break;
				default:
					versionSuffix = " (" + author + ")";
					break;
			}

			customer = name;

			return true;
		}
        internal static string GetVersion(string fileName)
        {
            string version, versionSuffix, author, customer;
            ParseFileName(fileName, out version, out versionSuffix, out author, out customer);

            // Soubor má mít název například: "Noris433990.cab", "Noris433990_1_Medictech.cab", "Noris430260_3500000.cab"
            return version;
        }
        /// <summary>
        /// Z textu odebere část před delimiterem a vrátí, text zkrátí tak že bude obsahovat část za delimiterem.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="delimiter"></param>
        /// <returns></returns>
		private static string _ReadPreDelim(ref string text, string delimiter)
		{
			if (String.IsNullOrEmpty(text)) return text;
			string result = "";
			int at = text.IndexOf(delimiter);
			if (at < 0)
			{
				result = text;
				text = "";
				return result;
			}
			else if (at < (text.Length - 1))
			{
				result = text.Substring(0, at);
				text = text.Substring(at + delimiter.Length);
			}
			return result;
		}
        /// <summary>
        /// Z verze a suffixu vrátí klíč do dictionary
        /// </summary>
        /// <param name="version"></param>
        /// <param name="versionSuffix"></param>
        /// <returns></returns>
		internal static string GetVersionKey(string version, string versionSuffix)
		{
			string result = version.Trim();
			if (!String.IsNullOrEmpty(versionSuffix))
				result += "_" + versionSuffix.Trim();
			return result.ToLower();
		}
        #endregion
        #region Načítání obsahu adresářů do seznamu balíčků
        /// <summary>
        /// Načte obsah daných adresářů a sestaví z něho seznam balíčků
        /// </summary>
        /// <param name="paths"></param>
        /// <returns></returns>
        internal static List<PackageSet> ReadContentOfDirs(IEnumerable<string> paths)
        {
            Dictionary<string, PackageSet> packZip = new Dictionary<string, PackageSet>();              // Pro klíč verze (verze + suffix od autora) obsahuje PackageSet
            Dictionary<string, PackageSet> authMax = new Dictionary<string, PackageSet>();              // Pro autora obsahuje jeho nejvyšší hotpatch (ne beta)
            Dictionary<string, List<PackageSet>> authLst = new Dictionary<string, List<PackageSet>>();  // Pro autora obsahuje seznam jeho PackageSet

            foreach (string path in paths)
            {
                // Načíst soubory z daného adresáře:
                System.IO.DirectoryInfo dirInfo = new System.IO.DirectoryInfo(path);
                foreach (System.IO.FileInfo fileInfo in dirInfo.EnumerateFiles("*.cab", System.IO.SearchOption.TopDirectoryOnly))
                {
                    // Co není explicitně ".cab", to ignoruji:
                    if (!String.Equals(fileInfo.Extension, ".cab", StringComparison.InvariantCultureIgnoreCase))
                        continue;

                    string version, versionSuffix, author, customer;
                    if (PackageSet.ParseFileName(fileInfo.Name, out version, out versionSuffix, out author, out customer))
                    {
                        // Jeden soubor = patří do balíčku PackageSet:
                        PackageSet pSet;
                        string versionKey = PackageSet.GetVersionKey(version, versionSuffix);      // Klíč = Verze+Suffix
                        if (!packZip.TryGetValue(versionKey, out pSet))
                        {
                            // Kompletace balíčků:
                            pSet = new PackageSet(version, versionSuffix, author);
                            packZip.Add(versionKey, pSet);

                            // Balíček do seznamu za autora:
                            List<PackageSet> listp;
                            if (!authLst.TryGetValue(author, out listp))
                            {
                                listp = new List<PackageSet>();
                                authLst.Add(author, listp);
                            }
                            listp.Add(pSet);

                            // Finální balíčky (patch nebo hotpatch s nulou na konci čísla: "43.38.50"):
                            if (pSet.IsPublic)
                            {
                                if (!authMax.ContainsKey(author))
                                    authMax.Add(author, pSet);
                                else
                                {	// Pokud aktuální verze je vyšší než dosavadní verze zachycená, pak zachytíme novou:
                                    if (PackageSet.CompareByVersion(pSet, authMax[author]) > 0)
                                        authMax[author] = pSet;
                                }
                            }
                        }
                        pSet.AddItem(fileInfo, customer);
                    }
                }
            }

            // Setřídím balíčky pro každého autora, sestupně podle čísla verze:
            // Sloučím zipem položky od jednotlivých autorů:
            List<PackageSet> pList = new List<PackageSet>();
            foreach (List<PackageSet> second in authLst.Values)
            {
                second.Sort(PackageSet.CompareByVersionDesc);
                pList = PackageSet.ZipList(pList, second, (a, b) => (a.MinWriteTime.Value < b.MinWriteTime.Value ? a : b));
            }

            return pList;
        }
        #endregion
        #region Práce s interními položkami (jednotlivé balíčky pro zakázky)
        /// <summary>
        /// Přidá 
        /// </summary>
        /// <param name="fileInfo"></param>
        /// <param name="customer"></param>
		internal void AddItem(System.IO.FileInfo fileInfo, string customer)
		{
            if (this.Items.Any(i => String.Equals(i.FileInfo.FullName, fileInfo.FullName, StringComparison.InvariantCultureIgnoreCase))) return;        // Duplicity neberu

			PackageItem item = new PackageItem(this, fileInfo, customer);
			this.Items.Add(item);

			DateTime writeTime = fileInfo.LastWriteTime;
			if (!this.MinWriteTime.HasValue || (this.MinWriteTime.HasValue && writeTime < this.MinWriteTime.Value))
				this.MinWriteTime = writeTime;
		}
        #endregion
        #region Komparátory
        /// <summary>
		/// Komparátor podle čísla verze, vzestupně
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		internal static int CompareByVersion(PackageSet a, PackageSet b)
		{
			if (a == null && b == null) return 0;
			if (a == null) return -1;
			if (b == null) return 1;

			string aVer = a.VersionSort;
			string bVer = b.VersionSort;
			if (aVer == null && bVer == null) return 0;
			if (aVer == null) return -1;
			if (bVer == null) return 1;
            return String.Compare(aVer, bVer);
		}
		/// <summary>
		/// Komparátor podle čísla verze, sestupně
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <returns></returns>
		internal static int CompareByVersionDesc(PackageSet a, PackageSet b)
		{
			return -CompareByVersion(a, b);
		}
        #endregion
        #region Zipování dvou seznamů do jednoho výsledného
        /// <summary>
		/// Spojí položky dvou listů do jednoho výsledného, s použitím selectoru.
		/// Spojuje je na střídačku jako zoubky zipu.
        /// Vstupní seznamy netřídí (nemění pořadí z jednotlivých seznamů), pouze vhodně prokládá data z jednoho a druhého seznamu.
		/// </summary>
		/// <param name="list1"></param>
		/// <param name="list2"></param>
		/// <param name="selector"></param>
		/// <returns></returns>
		internal static List<PackageSet> ZipList(List<PackageSet> list1, List<PackageSet> list2, Func<PackageSet, PackageSet, PackageSet> selector)
		{
			if (selector == null) return null;

			if (list1 == null && list2 == null) return new List<PackageSet>();
			if (list1 == null) return new List<PackageSet>(list2);
			if (list2 == null) return new List<PackageSet>(list1);

			int list1Cnt = list1.Count;
			int list2Cnt = list2.Count;
			if (list1Cnt == 0 && list2Cnt == 0) return new List<PackageSet>();
			if (list1Cnt == 0) return new List<PackageSet>(list2);
			if (list2Cnt == 0) return new List<PackageSet>(list1);

			// Oba listy nejsou null a oba mají více než 0 položek:
			List<PackageSet> result = new List<PackageSet>();
			int i1 = 0;
			int i2 = 0;
			while (i1 < list1Cnt || i2 < list2Cnt)
			{
				if (i1 < list1Cnt && i2 < list2Cnt)
				{	// Ukazatelé do obou polí ukazují na reálnou větu:
					PackageSet item = selector(list1[i1], list2[i2]);
					result.Add(item);
					if (Object.Equals(item, list1[i1]))
						i1++;
					else if (Object.Equals(item, list2[i2]))
						i2++;
					else
					{
						i1++;
						i2++;
					}
				}
				else if (i1 < list1Cnt)
				{	// Na reálnou větu ukazuje jen ukazatel do pole 1:
					PackageSet item = list1[i1];
					result.Add(item);
					i1++;
				}
				else if (i2 < list2Cnt)
				{	// Na reálnou větu ukazuje jen ukazatel do pole 2:
					PackageSet item = list2[i2];
					result.Add(item);
					i2++;
				}
				else
					break;
			}
			return result;
		}
        #endregion
    }
    /// <summary>
    /// Typ balíčku
    /// </summary>
    internal enum PackageVersionType
    {
        None = 0,
        HotpatchBetaOld,
        HotpatchBeta,
        Hotpatch,
        Patch,
        Verse
    }
	#endregion
	#region class PackageItem
	internal class PackageItem
	{
		internal PackageItem(PackageSet parent, System.IO.FileInfo fileInfo, string customer)
		{
			this.Parent = parent;
			this.FileInfo = fileInfo;
			this.Customer = customer;
		}
		internal PackageSet Parent { get; set; }
		internal System.IO.FileInfo FileInfo { get; set; }
		internal string Customer { get; set; }
		internal string Name
		{
			get
			{
				if (String.IsNullOrEmpty(this.Customer))
					return "Basic package";
				return this.Customer;
			}
		}
		public override string ToString()
		{
			return this.Name;
		}
		internal static int CompareByName(PackageItem a, PackageItem b)
		{
			if (a == null && b == null) return 0;
			if (a == null) return -1;
			if (b == null) return 1;

			return String.Compare(a.Customer, b.Customer, StringComparison.CurrentCultureIgnoreCase);
		}
	}
	#endregion
}
